﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Math;
using JigLibX.Utils;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PloobsEngine.Physics.XJigLib;
using PloobsEngine.Cameras;
using PloobsEngine.SceneControl;


namespace PloobsEngine.Physics
{    
    public class JigLibxWorld : IPhysicWorld
    {
        bool _isDebugDraw = false;
        PhysicsSystem physicSystem;
        ConstraintWorldPoint objectController = new ConstraintWorldPoint();
        ConstraintVelocity damperController = new ConstraintVelocity();
        GenericDebugDrawn _debug;
        Dictionary<Body, IPhysicObject> _reverseMap = new Dictionary<Body, IPhysicObject>();
        Dictionary<CollisionSkin, IPhysicObject> _reverseMapAlternative = new Dictionary<CollisionSkin, IPhysicObject>(); ///so usar em caso de triangle mesh
        static ImmovableSkinPredicate pred1 = new ImmovableSkinPredicate();
        static MovableSkinPredicate pred2 = new MovableSkinPredicate();
        static MovableSkinPredicatex pred3 = new MovableSkinPredicatex();
        static SttopedSkinPredicatex pred4 = new SttopedSkinPredicatex();
        static TrinagleSkinPredicate pred5 = new TrinagleSkinPredicate();

        List<IPhysicObject> obj;

        public JigLibxWorld()
        {
            _isDebugDraw = false;
            this.Init();
        }

        public JigLibxWorld(GenericDebugDrawn dd)
        {
            _isDebugDraw = true;
            this._debug = dd;
            this.Init();
        }

        #region IPhysicWorld Members

        public void Update(Microsoft.Xna.Framework.GameTime gt)
        {
            if (physicSystem != null)
            {
                float timeStep = (float)gt.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond;
                if (timeStep < 1.0f / 60.0f) physicSystem.Integrate(timeStep);
                else physicSystem.Integrate(1.0f / 60.0f);
            }
            
        }

        public void AddObject(IPhysicObject o)
        {
            JigLibXObject objs = o as JigLibXObject;
            if (o.PhysicObjectTypes == PhysicObjectTypes.NONE || o.PhysicObjectTypes == PhysicObjectTypes.GHOST )
            {                
                return;
            }

            if (objs.CollisionSkin_.Owner == null)
            {
                _reverseMapAlternative.Add(objs.CollisionSkin_, o);
            }
            else
            {
                _reverseMap.Add(objs.Body_, o);
            }
            objs.EnableBody();
            obj.Add(objs);
            
        }

        private void Init()
        {

            physicSystem = new PhysicsSystem();

            obj = new List<IPhysicObject>();

            physicSystem.CollisionSystem = new CollisionSystemSAP();

            physicSystem.EnableFreezing = true;
            physicSystem.SolverType = PhysicsSystem.Solver.Normal;
            physicSystem.CollisionSystem.UseSweepTests = true;
                        
            physicSystem.NumCollisionIterations = 10;
            physicSystem.NumContactIterations = 10;
            physicSystem.NumPenetrationRelaxtionTimesteps = 15;

        }

        public void DebugDrawn(GameTime gt , ICamera cam )
        {
            VertexPositionColor[] wf;
            foreach (JigLibXObject ph in obj)
            {
                //wf = BodyRenderExtensions.GetBodyWireframe(ph.Body);
                wf = BodyRenderExtensions.GetLocalSkinWireframe(ph.CollisionSkin_);
                
                if (ph.Body_.CollisionSkin != null)
                {
                    //wf = BodyRenderExtensions.GetBodyWireframe(ph.Body);
                    ph.Body_.TransformWireframe(wf);                     
                }
                _debug.DrawShape(wf);
                _debug.Draw(gt, cam,this);    
            }
        }

        #endregion

        #region IPhysicWorld Members

        public void RemoveObject(IPhysicObject o)
        {
            JigLibXObject objs = o as JigLibXObject;
            if (o.PhysicObjectTypes == PhysicObjectTypes.NONE|| o.PhysicObjectTypes == PhysicObjectTypes.GHOST )
            {                
                return;
            }
            if (objs.CollisionSkin_.Owner == null)
            {
                _reverseMapAlternative.Remove(objs.CollisionSkin_);
            }
            else
            {
                _reverseMap.Remove(objs.Body_);
            }

            obj.Remove(objs);   
            o.Disable();
        }

        #endregion

        #region IPhysicWorld Members

        public bool isDebugDraw
        {
            get
            {
                return _isDebugDraw;
            }
            set
            {
                this._isDebugDraw = value;
            }
        }

        #endregion

        #region IPhysicWorld Members
               

        public SegmentInterceptInfo SegmentIntersect(Microsoft.Xna.Framework.Ray raio, float maxDistance,CollisionSkinPredicate1 predicate)
        {
            Vector3 ray = new Vector3(0f, 0f, 0f);
            float dist = 0;
            CollisionSkin skin = null;
            Vector3 pos = Vector3.Zero;
            Vector3 normal = Vector3.Zero;

            Segment seg = new Segment(raio.Position, raio.Direction * 10000);
            physicSystem.CollisionSystem.SegmentIntersect(out dist, out skin, out pos, out normal, seg, predicate);        

            float distance = -1;

            if (skin != null)
            {
                distance = Vector3.Distance(pos, raio.Position);
                if (maxDistance > 0)
                {
                    if (distance > maxDistance)
                    {
                        return null;
                    }
                }

                SegmentInterceptInfo si = new SegmentInterceptInfo();
                si.Distance = distance;
                si.ImpactNormal = normal;
                si.ImpactPosition = pos;
                if (skin.Owner == null)
                {
                    si.PhysicObject = _reverseMapAlternative[skin];
                }
                else
                {
                    si.PhysicObject = _reverseMap[skin.Owner];
                }
                return si;

            }
            return null;
        }

        public SegmentInterceptInfo SegmentIntersect(Microsoft.Xna.Framework.Ray raio, SegmentInterceptMethod method, float maxDistance)
        {
            Vector3 ray = new Vector3(0f, 0f, 0f);
            float dist=0;
            CollisionSkin skin = null;
            Vector3 pos = Vector3.Zero;
            Vector3  normal = Vector3.Zero;
            
            Segment seg = new Segment(raio.Position, raio.Direction * 500);
            switch (method)
            {
                case SegmentInterceptMethod.ALL:
                    physicSystem.CollisionSystem.SegmentIntersect(out dist, out skin, out pos, out normal, seg, pred2);
                    break;
                case SegmentInterceptMethod.NO_TRINAGLEMESH:
                    physicSystem.CollisionSystem.SegmentIntersect(out dist, out skin, out pos, out normal, seg, pred1);
                    break;           
                case SegmentInterceptMethod.ONLY_MOBILES:
                    physicSystem.CollisionSystem.SegmentIntersect(out dist, out skin, out pos, out normal, seg, pred3);
                    break;
                case SegmentInterceptMethod.ONLY_STOPPEDS:
                    physicSystem.CollisionSystem.SegmentIntersect(out dist, out skin, out pos, out normal, seg, pred4);
                    break;
                case SegmentInterceptMethod.ONLY_TRIANGLEMESH:
                    physicSystem.CollisionSystem.SegmentIntersect(out dist, out skin, out pos, out normal, seg, pred5);
                    break;

            }            

            float distance = -1;

            if (skin != null )
            {
                distance = Vector3.Distance(pos,raio.Position);
                if (maxDistance > 0)
                {
                    if (distance > maxDistance)
                    {
                        return null;
                    }
                }

                    SegmentInterceptInfo si = new SegmentInterceptInfo();
                    si.Distance = distance;
                    si.ImpactNormal = normal;
                    si.ImpactPosition = pos;
                    if (skin.Owner == null)
                    {                        
                        si.PhysicObject = _reverseMapAlternative[skin];                        
                    }
                    else
                    {
                        if (_reverseMap.ContainsKey(skin.Owner))
                        {
                            si.PhysicObject = _reverseMap[skin.Owner];
                        }
                        else
                        {
                            return null;
                        }
                    }
                    return si;
                
            }
            return null;
        }

        public SegmentInterceptInfo SegmentIntersect(Microsoft.Xna.Framework.Ray raio)
        {
            return this.SegmentIntersect(raio, SegmentInterceptMethod.NO_TRINAGLEMESH,0);
        }

        #endregion

        #region IPhysicWorld Members


        public GenericDebugDrawn DebugDraw
        {
            set { this._debug = value; }
        }

        #endregion

        #region IPhysicWorld Members


        public void Serialize(System.Xml.XmlTextWriter textWriter)
        {
         //physicSystem.CollisionSystem.DetectCollisions(new Body(),new Col   
        }

        public void DeSerialize(System.Xml.XmlNode node, EngineStuff engine)
        {
            GenericDebugDrawn dd = new GenericDebugDrawn(engine);
            this._debug = dd;
            Init();
        }

        #endregion

        cskin csk = new cskin();
        public List<IPhysicObject> DetectCollisions(IPhysicObject po)
        {
            if (po is JigLibXObject)
            {
                JigLibXObject p = po as JigLibXObject;
                csk.ClearCollisions();
                physicSystem.CollisionSystem.DetectCollisions(p.Body_, null,csk , 0.01f);
                //IEnumerable<IPhysicObject> pobs =csk.Collisions.Select<CollisionSkin,IPhysicObject>( (a) => _reverseMapAlternative[a]);
                List<IPhysicObject> resp = new List<IPhysicObject>();
                foreach (var item in csk.Collisions)
                {
                    if (item.Owner == null)
                    {
                        resp.Add(_reverseMapAlternative[item]);
                    }
                    else
                    {
                        resp.Add(_reverseMap[item.Owner]);
                    }
                }
                return resp;
                
            }
            return null;
        }

        
        class cskin : CollisionSkinPredicate2
        {
            List<CollisionSkin> collisions = new List<CollisionSkin>();

            public List<CollisionSkin> Collisions
            {
                get { return collisions; }                
            }

            public void ClearCollisions()
            {
                collisions.Clear();
            }

            public override bool ConsiderSkinPair(CollisionSkin skin0, CollisionSkin skin1)
            {
                
                collisions.Add(skin1);
                return false;                
            }
        }


        #region IPhysicWorld Members


        public List<IPhysicObject> PhysicObjects
        {
            get { return obj; }
        }

        #endregion

        #region IPhysicWorld Members


        public List<IPhysicObject> GetPhysicsObjectsInRange(IPhysicObject po, float distance, CullerConditionAvaliator<IPhysicObject, IObject> CullerAvaliator)
        {
            List<IPhysicObject> resp = new List<IPhysicObject>();
            foreach (var item in obj)
            {
                if (CullerAvaliator(item, item.ObjectOwner))
                {
                    if (item != po && Vector3.Distance(po.Position, item.Position) <= distance)
                    {
                        resp.Add(item);
                    }
                }
            }
            return resp;
        }

        #endregion
    }

}




    class ImmovableSkinPredicate : CollisionSkinPredicate1
    {
        public override bool ConsiderSkin(CollisionSkin skin0)
        {
            if (skin0.Owner != null)
            {
                return true;
            }
            return false;

        }
    }

    class SttopedSkinPredicatex : CollisionSkinPredicate1
    {
        public override bool ConsiderSkin(CollisionSkin skin0)
        {
            if (skin0.Owner == null || skin0.Owner.Velocity == Vector3.Zero)
            {
                return true;
            }
            return false;
        }
    }

    class MovableSkinPredicatex : CollisionSkinPredicate1
    {
        public override bool ConsiderSkin(CollisionSkin skin0)
        {
            if (skin0.Owner!=null && skin0.Owner.Immovable == false)
            {
                return true;
            }
            return false;
        }
    }


    class MovableSkinPredicate : CollisionSkinPredicate1
    {
        public override bool ConsiderSkin(CollisionSkin skin0)
        {
           return true;
           
        }
    }
    class TrinagleSkinPredicate : CollisionSkinPredicate1
    {
        public override bool ConsiderSkin(CollisionSkin skin0)
        {
            if (skin0.Owner == null)
            {
                return true;
            }
            return false;

        }
    }
    

   


